package org.ricks.log;

import java.io.File;
import java.util.*;


public final class DynamicPath {

    private static final String DEFAULT_DATE_FORMAT_PATTERN = "yyyy-MM-dd";

    private final List<Segment> segments;
    private final File folder;
    private final List<String> plainTexts;
    private final String suffix;

    
    public DynamicPath(final String path) {
        String normalizedPath = path.replace('/', File.separatorChar);

        segments = new ArrayList<Segment>();
        plainTexts = new ArrayList<String>();

        String prefix = "";
        int start = 0;
        boolean token = false;

        for (int i = 0; i < normalizedPath.length(); ++i) {
            char character = normalizedPath.charAt(i);

            if (character == '{') {
                if (token) {
                    throw new IllegalArgumentException("Closing curly bracket is missing: '" + path + "'");
                } else if (i > 0 && normalizedPath.charAt(i - 1) == '}') {
                    throw new IllegalArgumentException("Two patterns must be separated by at least one character: '" + path + "'");
                } else {
                    if (i > start) {
                        if (prefix.isEmpty() && segments.isEmpty()) {
                            prefix = normalizedPath.substring(0, i);
                        }

                        String text = normalizedPath.substring(start, i);
                        segments.add(new PlainTextSegment(text));
                        plainTexts.add(text);
                    }

                    start = i + 1;
                    token = true;
                }
            } else if (character == '}') {
                if (token) {
                    segments.add(parseSegment(normalizedPath, normalizedPath.substring(start, i)));
                    start = i + 1;
                    token = false;
                } else {
                    throw new IllegalArgumentException("Opening curly bracket is missing: '" + path + "'");
                }
            }
        }

        if (token) {
            throw new IllegalArgumentException("Closing curly bracket is missing: '" + path + "'");
        } else if (start < normalizedPath.length() - 1) {
            if (prefix.isEmpty() && segments.isEmpty()) {
                prefix = normalizedPath;
            }

            String text = normalizedPath.substring(start);
            int separator = Math.max(text.lastIndexOf(File.separatorChar), text.lastIndexOf('/'));
            segments.add(new PlainTextSegment(text));
            plainTexts.add(text);
            suffix = separator == -1 ? text : text.substring(separator + 1);
        } else {
            suffix = "";
        }

        int separator = Math.max(prefix.lastIndexOf(File.separatorChar), prefix.lastIndexOf('/'));
        folder = new File(separator == -1 ? "" : prefix.substring(0, separator)).getAbsoluteFile();
    }

    
    public String resolve() {
        Timestamp timestamp = RuntimeProvider.createTimestamp();
        StringBuilder builder = new StringBuilder();
        for (Segment segment : segments) {
            builder.append(segment.createToken(builder.toString(), timestamp));
        }
        return builder.toString();
    }

    
    public List<FileTuple> getAllFiles(final String backupSuffix) {
        List<FileTuple> tuples = new ArrayList<FileTuple>();

        Collection<File> originals = backupSuffix == null ? new ArrayList<File>() : new HashSet<File>();
        collectFiles(folder, suffix, originals);

        if (backupSuffix != null) {
            Collection<File> backups = new ArrayList<File>();
            collectFiles(folder, suffix + backupSuffix, backups);

            for (File backup : backups) {
                String path = backup.getAbsolutePath();
                File original = new File(path.substring(0, path.length() - backupSuffix.length()));
                tuples.add(new FileTuple(original, backup));
                originals.remove(original);
            }
        }

        for (File original : originals) {
            String path = original.getAbsolutePath();
            File backup = new File(backupSuffix == null ? path : path + backupSuffix);
            tuples.add(new FileTuple(original, backup));
        }

        Collections.sort(tuples, LastModifiedFileTupleComparator.INSTANCE);
        return tuples;
    }

    
    public boolean isValid(final File file) {
        return isValid(file.getAbsolutePath(), 0, 0);
    }

    
    private void collectFiles(final File folder, final String suffix, final Collection<File> found) {
        File[] files = folder.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    collectFiles(file, suffix, found);
                } else if (file.isFile() && file.getAbsolutePath().endsWith(suffix)) {
                    int index = 0;

                    for (String text : plainTexts) {
                        index = file.getAbsolutePath().indexOf(text, index);
                        if (index == -1) {
                            break;
                        } else {
                            index += text.length();
                        }
                    }

                    if (index >= 0) {
                        found.add(file.getAbsoluteFile());
                    }
                }
            }
        }
    }

    
    private boolean isValid(final String path, final int pathPosition, final int segmentIndex) {
        Segment segment = segments.get(segmentIndex);
        String expectedValue = segment.getStaticText();

        if (pathPosition == 0) {
            File file = new File(expectedValue == null ? "" : expectedValue).getAbsoluteFile();
            if (file.isDirectory()) {
                expectedValue = file.getAbsolutePath() + File.separator;
            } else {
                expectedValue = file.getAbsolutePath();
            }
        }

        if (expectedValue == null) {
            if (segmentIndex == segments.size() - 1) {
                return segment.validateToken(path.substring(pathPosition));
            } else {
                String nextValue = segments.get(segmentIndex + 1).getStaticText();
                for (int i = path.indexOf(nextValue, pathPosition); i >= 0; i = path.indexOf(nextValue, i + 1)) {
                    if (segment.validateToken(path.substring(pathPosition, i)) && isValid(path, i, segmentIndex + 1)) {
                        return true;
                    }
                }
                return false;
            }
        } else if (path.startsWith(expectedValue, pathPosition)) {
            if (segmentIndex == segments.size() - 1) {
                return pathPosition + expectedValue.length() == path.length();
            } else {
                return isValid(path, pathPosition + expectedValue.length(), segmentIndex + 1);
            }
        } else {
            return false;
        }
    }

    
    private static Segment parseSegment(final String path, final String token) {
        int separator = token.indexOf(':');

        String name;
        String parameter;

        if (separator == -1) {
            name = token.trim();
            parameter = null;
        } else {
            name = token.substring(0, separator).trim();
            parameter = token.substring(separator + 1).trim();
        }

        if ("date".equals(name)) {
            return new DateSegment(parameter == null ? DEFAULT_DATE_FORMAT_PATTERN : parameter);
        } else {
            throw new IllegalArgumentException("Invalid token '" + token + "' in '" + path + "'");
        }
    }

}
